// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: jhkim, lucasyoo

#pragma once

#include <algorithm>
#include <glog/logging.h>
#include <nlohmann/json.hpp>
#include <memory>
#include <set>

#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin/proto/coin_order_enums.pb.h"

namespace coin2::strategy::cmm_strat {

using coin2::exchange::base::feed::FeedUpdate;
using ::coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::IProductInfo;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin::proto::OrderDirection;

uint64_t GetProductId(const IProduct& product);


/*
 CmmPricer
 This class is responsible for calculating the best price for a given product.
 It is also responsible for calculating the best price for a given product
 */
class CmmPricer {
 public:
  virtual std::set<uint64_t> GetProducts() = 0;
  virtual const IProduct* GetProductTarget() = 0;
  virtual std::optional<double> GetMid() = 0;
  virtual const IProductInfo* GetPI() = 0;
  virtual void onBookFeed(const FeedUpdate& upd) = 0;
  virtual void onTradeFeed(const FeedUpdate& upd) = 0;
  virtual std::pair<double, double> GetFairPrice() = 0;
};

/*
 CmmTriArbPricer
 This class is responsible for calculating the best price for a given product (product_a_b, e.g. DKA/BTC).
 It uses product_b_usdt, product_c_usdt to calculate the best price for product_a_b.
 */
class CmmTriArbPricer : public CmmPricer {
 public:
  // product_a_b = product_b_usdt / product_c_usdt
  CmmTriArbPricer(
      const ::nlohmann::json& tri_arb_config,
      std::optional<int64_t> ts);


  std::set<uint64_t> GetProducts() final { return product_ids_; }
  std::pair<double, double> GetFairPrice() final { return fair_price_; }
  const IProduct* GetProductTarget() final { return product_a_b_.get(); }
  std::optional<double> GetMid() final;
  void onBookFeed(const FeedUpdate& upd) final;
  void onTradeFeed(const FeedUpdate& upd) final { return ; }


  // product info related
  std::unique_ptr<IProduct> product_a_b_;
  std::unique_ptr<IProduct> product_b_usdt_;
  std::unique_ptr<IProduct> product_c_usdt_;
  std::set<uint64_t> product_ids_;
  ProductEncyclopedia product_cache_;
  const IProductInfo* pi_;
  const IProductInfo* GetPI() { return pi_; }

  // bid/ask price of products
  std::pair<double, double> price_a_b_bbo_ {0.0, 0.0};
  std::pair<double, double> price_b_usdt_bbo_ {0.0, 0.0};
  std::pair<double, double> price_c_usdt_bbo_ {0.0, 0.0};

  // extra_bp for pricing
  double extra_bp_{0.0};
  double maker_fee_bp_{0.0};
  double taker_fee_bp_{0.0};

  // Priced Result
  std::pair<double, double> fair_price_ {0.0, 0.0};

};

/*
 CmmSelfMidpPricer
 This class is responsible for calculating the best price for a given product (product_a, e.g. MKC/USDT).
 It uses it's bbo price to calculate the best price for product_a.
 */
class CmmSelfMidpPricer : public CmmPricer {
 public:
  CmmSelfMidpPricer(
      const ::nlohmann::json& self_mid_config,
      std::optional<int64_t> ts);

  std::set<uint64_t> GetProducts() final { return product_ids_; }
  const IProductInfo* GetPI() { return pi_; }
  const IProduct* GetProductTarget() final { return product_a_.get(); }
  std::pair<double, double> GetFairPrice() final { return fair_price_; }
  std::optional<double> GetMid() final;
  void onBookFeed(const FeedUpdate& upd) final;
  void onTradeFeed(const FeedUpdate& upd) final { return; }

  // product info related
  std::unique_ptr<IProduct> product_a_;
  std::set<uint64_t> product_ids_;
  ProductEncyclopedia product_cache_;
  const IProductInfo* pi_;

  // bid/ask price of products
  std::pair<double, double> price_a_bbo_ {0.0, 0.0};

  // extra_bp for pricing
  double extra_bp_{0.0};

  // Priced Result
  std::pair<double, double> fair_price_ {0.0, 0.0};


};

}  // namespace coin2::strategy::cmm_strat
